package kotlinx.coroutines.experimental.io

import kotlinx.coroutines.experimental.TestBase
import kotlinx.coroutines.experimental.channels.ClosedReceiveChannelException
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.runBlocking
import kotlinx.coroutines.experimental.yield
import org.junit.Test
import java.io.IOException
import kotlin.test.assertEquals
import kotlin.test.fail

class ByteBufferChannelScenarioTest : TestBase() {
    private val ch = ByteBufferChannel(true)

    @Test
    fun testReadBeforeAvailable() {
        expect(1)

        runBlocking {
            launch(coroutineContext) {
                expect(3)

                val bb = ByteBuffer.allocate(10)
                val rc = ch.readAvailable(bb) // should suspend

                expect(5)
                assertEquals(4, rc)

                expect(6)
            }

            expect(2)
            yield()

            expect(4)
            ch.writeInt(0xff) // should resume

            yield()

            finish(7)
        }
    }

    @Test
    fun testReadBeforeAvailable2() {
        expect(1)

        runBlocking {
            launch(coroutineContext) {
                expect(3)

                val bb = ByteBuffer.allocate(4)
                ch.readFully(bb) // should suspend

                expect(5)

                bb.flip()
                assertEquals(4, bb.remaining())

                expect(6)
            }

            expect(2)
            yield()

            expect(4)
            ch.writeInt(0xff) // should resume

            yield()

            finish(7)
        }
    }

    @Test
    fun testReadAfterAvailable() {
        expect(1)

        runBlocking {
            ch.writeInt(0xff) // should resume

            launch(coroutineContext) {
                expect(3)

                val bb = ByteBuffer.allocate(10)
                val rc = ch.readAvailable(bb) // should NOT suspend

                expect(4)
                assertEquals(4, rc)

                expect(5)
            }

            expect(2)
            yield()

            finish(6)
        }
    }

    @Test
    fun testReadAfterAvailable2() {
        expect(1)

        runBlocking {
            ch.writeInt(0xff) // should resume

            launch(coroutineContext) {
                expect(3)

                val bb = ByteBuffer.allocate(4)
                ch.readFully(bb) // should NOT suspend

                expect(4)
                bb.flip()
                assertEquals(4, bb.remaining())

                expect(5)
            }

            expect(2)
            yield()

            finish(6)
        }
    }

    @Test
    fun testReadToEmpty() {
        runBlocking {
            expect(1)

            val rc = ch.readAvailable(ByteBuffer.allocate(0))

            expect(2)

            assertEquals(0, rc)

            finish(3)
        }
    }

    @Test
    fun testReadToEmptyFromFailedChannel() {
        runBlocking {
            expect(1)

            ch.close(IOException())

            try {
                ch.readAvailable(ByteBuffer.allocate(0))
                fail("Should throw exception")
            } catch (expected: IOException) {
            }

            finish(2)
        }
    }

    @Test
    fun testReadToEmptyFromClosedChannel() {
        runBlocking {
            expect(1)

            ch.close()

            val rc = ch.readAvailable(ByteBuffer.allocate(0))

            expect(2)

            assertEquals(-1, rc)

            finish(3)
        }
    }

    @Test
    fun testReadFullyToEmptyFromClosedChannel() {
        runBlocking {
            expect(1)

            ch.close()

            ch.readFully(ByteBuffer.allocate(0))

            finish(2)
        }
    }

    @Test
    fun testReadFullyFromClosedChannel() {
        runBlocking {
            expect(1)

            ch.close()
            try {
                ch.readFully(ByteBuffer.allocate(1))
                fail("Should throw exception")
            } catch (expected: ClosedReceiveChannelException) {
            }

            finish(2)
        }
    }

    @Test
    fun testReadFullyToEmptyFromFailedChannel() {
        runBlocking {
            expect(1)

            ch.close(IOException())

            try {
                ch.readFully(ByteBuffer.allocate(0))
                fail("Should throw exception")
            } catch (expected: IOException) {
            }

            finish(2)
        }
    }

    @Test
    fun testWriteBlock() {
        runBlocking {
            launch(coroutineContext) {
                expect(1)

                ch.write {
                    it.putLong(0x1234567812345678L)
                }

                expect(2)
            }

            yield()
            expect(3)

            assertEquals(0x1234567812345678L, ch.readLong())
            assertEquals(0, ch.availableForRead)

            finish(4)
        }
    }

    @Test
    fun testWriteBlockSuspend() {
        runBlocking {
            launch(coroutineContext) {
                expect(1)

                ch.writeFully(ByteArray(4088))

                expect(2)

                ch.write(8) {
                    it.putLong(0x1234567812345678L)
                }

                expect(4)
            }

            yield()
            expect(3)

            ch.readFully(ByteArray(9))
            yield()
            expect(5)

            ch.readFully(ByteArray(4088 - 9))

            expect(6)

            assertEquals(0x1234567812345678L, ch.readLong())
            assertEquals(0, ch.availableForRead)

            finish(7)
        }
    }

    @Test
    fun testReadBlock() = runBlocking {
        ch.writeLong(0x1234567812345678L)

        ch.read {
            assertEquals(0x1234567812345678L, it.getLong())
        }

        finish(1)
    }

    @Test
    fun testReadBlockSuspend() = runBlocking {
        ch.writeByte(0x12)

        launch(coroutineContext) {
            expect(1)
            ch.read(8) {
                assertEquals(0x1234567812345678L, it.getLong())
            }

            expect(3)
        }

        yield()
        expect(2)

        ch.writeLong(0x3456781234567800L)
        yield()

        expect(4)
        ch.readByte()
        assertEquals(0, ch.availableForRead)

        finish(5)
    }

    @Test
    fun testReadBlockSuspend2() = runBlocking {
        launch(coroutineContext) {
            expect(1)
            ch.read(8) {
                assertEquals(0x1234567812345678L, it.getLong())
            }

            expect(3)
        }

        yield()
        expect(2)

        ch.writeLong(0x1234567812345678L)
        yield()

        expect(4)
        assertEquals(0, ch.availableForRead)

        finish(5)
    }
}